﻿
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;


namespace IlGenUtil
{
	/// <summary>
	/// Интерфейс, который реализуют функции.
	/// Объекты функций создаются с помощью Type.Method
	/// </summary>
	/// <typeparam name="Args">Список аргументов функции (Nil, если их нету)</typeparam>
	/// <typeparam name="Ret">Возвращаемое значение функции (вместо void используется Nil)</typeparam>
	public interface StaticMethod<Args, Ret>
	{
		MethodInfo m { get; }
	}

	public interface VirtualMethod<Args, Ret>
	{
		MethodInfo m { get; }
	}

	public static class Type<T>
	{
		public struct _SFun<Args, Ret> : StaticMethod<Args, Ret>
		{
			public MethodInfo m { get { return this._m; } }
			private readonly MethodInfo _m;

			public _SFun(MethodInfo m)
			{
				// TODO: проверить, что метод статический
				this._m = m;
			}
		}

		public struct _VFun<Args, Ret> : VirtualMethod<Args, Ret>
		{
			public MethodInfo m { get { return this._m; } }
			private readonly MethodInfo _m;

			public _VFun(MethodInfo m)
			{
				// TODO: проверить, что метод виртуальный
				this._m = m;
			}
		}


		private static MethodInfo MethodAux<Ret>(String name, Type[] types)
		{
			var m = typeof(T).GetMethod(name, types);
			if (m == null)
			{
				throw new ArgumentException("No such method");
			}
			var retType = typeof(Ret) == typeof(Nil) ? typeof(void) : typeof(Ret);
			if (m.ReturnType != retType)
			{
				throw new ArgumentException("Invalid return type specified");
			}
			return m;
		}


		#region static method generators
		
		public static StaticMethod<Nil, Nil> StaticMethod(String name)
		{
			return new _SFun<Nil, Nil>(MethodAux<Nil>(name, new Type[] { }));
		}

		public static StaticMethod<Nil, R> StaticMethod<R>(String name)
		{
			return new _SFun<Nil, R>(MethodAux<R>(name, new Type[] { }));
		}
		public static StaticMethod<Lst<A0,Lst<T, Nil>>, R> StaticMethod<A0, R>(String name)
		{
			return new _SFun<Lst<A0,Lst<T, Nil>>, R>(MethodAux<R>(name, new Type[] { typeof(A0) }));
		}

		public static StaticMethod<Lst<A0,Lst<A1,Lst<T, Nil>>>, R> StaticMethod<A1, A0, R>(String name)
		{
			return new _SFun<Lst<A0,Lst<A1,Lst<T, Nil>>>, R>(MethodAux<R>(name, new Type[] { typeof(A1), typeof(A0) }));
		}

		public static StaticMethod<Lst<A0,Lst<A1,Lst<A2,Lst<T, Nil>>>>, R> StaticMethod<A2, A1, A0, R>(String name)
		{
			return new _SFun<Lst<A0,Lst<A1,Lst<A2,Lst<T, Nil>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A2), typeof(A1), typeof(A0) }));
		}

		public static StaticMethod<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<T, Nil>>>>>, R> StaticMethod<A3, A2, A1, A0, R>(String name)
		{
			return new _SFun<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<T, Nil>>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A3), typeof(A2), typeof(A1), typeof(A0) }));
		}

		public static StaticMethod<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<T, Nil>>>>>>, R> StaticMethod<A4, A3, A2, A1, A0, R>(String name)
		{
			return new _SFun<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<T, Nil>>>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A4), typeof(A3), typeof(A2), typeof(A1), typeof(A0) }));
		}

		public static StaticMethod<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<A5,Lst<T, Nil>>>>>>>, R> StaticMethod<A5, A4, A3, A2, A1, A0, R>(String name)
		{
			return new _SFun<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<A5,Lst<T, Nil>>>>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A5), typeof(A4), typeof(A3), typeof(A2), typeof(A1), typeof(A0) }));
		}

		
		#endregion


		#region virtual method generators
		public static VirtualMethod<Lst<T, Nil>, Nil> VirtualMethod(String name)
		{
			return new _VFun<Lst<T, Nil>, Nil>(MethodAux<Nil>(name, new Type[] { }));
		}

		public static VirtualMethod<Lst<T, Nil>, R> VirtualMethod<R>(String name)
		{
			return new _VFun<Lst<T, Nil>, R>(MethodAux<R>(name, new Type[] { }));
		}
		public static VirtualMethod<Lst<A0,Lst<T, Nil>>, R> VirtualMethod<A0, R>(String name)
		{
			return new _VFun<Lst<A0,Lst<T, Nil>>, R>(MethodAux<R>(name, new Type[] { typeof(A0) }));
		}

		public static VirtualMethod<Lst<A0,Lst<A1,Lst<T, Nil>>>, R> VirtualMethod<A1, A0, R>(String name)
		{
			return new _VFun<Lst<A0,Lst<A1,Lst<T, Nil>>>, R>(MethodAux<R>(name, new Type[] { typeof(A1), typeof(A0) }));
		}

		public static VirtualMethod<Lst<A0,Lst<A1,Lst<A2,Lst<T, Nil>>>>, R> VirtualMethod<A2, A1, A0, R>(String name)
		{
			return new _VFun<Lst<A0,Lst<A1,Lst<A2,Lst<T, Nil>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A2), typeof(A1), typeof(A0) }));
		}

		public static VirtualMethod<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<T, Nil>>>>>, R> VirtualMethod<A3, A2, A1, A0, R>(String name)
		{
			return new _VFun<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<T, Nil>>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A3), typeof(A2), typeof(A1), typeof(A0) }));
		}

		public static VirtualMethod<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<T, Nil>>>>>>, R> VirtualMethod<A4, A3, A2, A1, A0, R>(String name)
		{
			return new _VFun<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<T, Nil>>>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A4), typeof(A3), typeof(A2), typeof(A1), typeof(A0) }));
		}

		public static VirtualMethod<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<A5,Lst<T, Nil>>>>>>>, R> VirtualMethod<A5, A4, A3, A2, A1, A0, R>(String name)
		{
			return new _VFun<Lst<A0,Lst<A1,Lst<A2,Lst<A3,Lst<A4,Lst<A5,Lst<T, Nil>>>>>>>, R>(MethodAux<R>(name, new Type[] { typeof(A5), typeof(A4), typeof(A3), typeof(A2), typeof(A1), typeof(A0) }));
		}

				
				#endregion
			}
			
			
			public static class FunctionCallExtensions
			{
				// call function with 0 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, S, Args, Ret>(this IlGen<S, Args, Ret> g, StaticMethod<Nil, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<S, Args, Ret>(this IlGen<S, Args, Ret> g, StaticMethod<Nil, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, S, Args, Ret>(this IlGen<S, Args, Ret> g, VirtualMethod<Nil, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<S, Args, Ret>(this IlGen<S, Args, Ret> g, VirtualMethod<Nil, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				// call function with 1 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A0, S, Args, Ret>(this IlGen<Lst<A0, S>, Args, Ret> g, StaticMethod<Lst<A0, Nil>, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<A0, S, Args, Ret>(this IlGen<Lst<A0, S>, Args, Ret> g, StaticMethod<Lst<A0, Nil>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A0, S, Args, Ret>(this IlGen<Lst<A0, S>, Args, Ret> g, VirtualMethod<Lst<A0, Nil>, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<A0, S, Args, Ret>(this IlGen<Lst<A0, S>, Args, Ret> g, VirtualMethod<Lst<A0, Nil>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				// call function with 2 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A1, A0, S, Args, Ret>(this IlGen<Lst<A1, Lst<A0, S>>, Args, Ret> g, StaticMethod<Lst<A1, Lst<A0, Nil>>, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<A1, A0, S, Args, Ret>(this IlGen<Lst<A1, Lst<A0, S>>, Args, Ret> g, StaticMethod<Lst<A1, Lst<A0, Nil>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A1, A0, S, Args, Ret>(this IlGen<Lst<A1, Lst<A0, S>>, Args, Ret> g, VirtualMethod<Lst<A1, Lst<A0, Nil>>, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<A1, A0, S, Args, Ret>(this IlGen<Lst<A1, Lst<A0, S>>, Args, Ret> g, VirtualMethod<Lst<A1, Lst<A0, Nil>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				// call function with 3 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A2, Lst<A1, Lst<A0, S>>>, Args, Ret> g, StaticMethod<Lst<A2, Lst<A1, Lst<A0, Nil>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A2, Lst<A1, Lst<A0, S>>>, Args, Ret> g, StaticMethod<Lst<A2, Lst<A1, Lst<A0, Nil>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A2, Lst<A1, Lst<A0, S>>>, Args, Ret> g, VirtualMethod<Lst<A2, Lst<A1, Lst<A0, Nil>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A2, Lst<A1, Lst<A0, S>>>, Args, Ret> g, VirtualMethod<Lst<A2, Lst<A1, Lst<A0, Nil>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				// call function with 4 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>, Args, Ret> g, StaticMethod<Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>, Args, Ret> g, StaticMethod<Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>, Args, Ret> g, VirtualMethod<Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>, Args, Ret> g, VirtualMethod<Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				// call function with 5 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>, Args, Ret> g, StaticMethod<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>, Args, Ret> g, StaticMethod<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>, Args, Ret> g, VirtualMethod<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>, Args, Ret> g, VirtualMethod<Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				// call function with 6 arguments
				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A5, A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>>, Args, Ret> g, StaticMethod<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

						public static IlGen<S, Args, Ret> Call<A5, A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>>, Args, Ret> g, StaticMethod<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Call, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

				public static IlGen<Lst<R, S>, Args, Ret> Call<R, A5, A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>>, Args, Ret> g, VirtualMethod<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>>, R> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<Lst<R, S>, Args, Ret>(g.il);
				}

				public static IlGen<S, Args, Ret> Call<A5, A4, A3, A2, A1, A0, S, Args, Ret>(this IlGen<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, S>>>>>>, Args, Ret> g, VirtualMethod<Lst<A5, Lst<A4, Lst<A3, Lst<A2, Lst<A1, Lst<A0, Nil>>>>>>, Nil> f)
				{
					g.il.EmitCall(OpCodes.Callvirt, f.m, null);
					return new IlGen<S, Args, Ret>(g.il);
				}

						
					}
				}
